package com.yixia.framework.dataScope;

import cn.hutool.core.convert.Convert;
import com.yixia.common.core.domain.BaseEntity;
import com.yixia.common.core.domain.model.LoginUser;
import com.yixia.framework.security.context.PermissionContextHolder;
import com.yixia.common.annotation.DataScope;
import com.yixia.common.core.domain.entity.SysRole;
import com.yixia.common.core.domain.entity.SysUser;

import com.yixia.common.utils.SecurityUtils;
import com.yixia.common.utils.StringUtils;

import lombok.AllArgsConstructor;
import lombok.Data;

import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 数据权限处理器
 *
 * @author: 依梦
 * @Date 2023/8/17
 */
//@Aspect
@Slf4j
@Component
public class MybatisPlusPermissionHandler {


    /**
     * 通过ThreadLocal记录权限相关的属性值
     * 本地线程变量，每个线程其存储的值都不一样
     */
    private static ThreadLocal<DataScopeParam> threadLocal = new ThreadLocal<>();

    /**
     * 清空当前线程上次保存的权限信息
     */
    @After("dataScopePointCut()")
    public void clearThreadLocal() {
        threadLocal.remove();
        log.debug("threadLocal.remove()");
    }


    /**
     * 注解对象
     * 该成员变量在并发情况下导致多个线程公用了一套 @DataScope 配置的参数，导致sql拼接失败，必须改为局部变量
     * 修改于：2022.04.28
     */
    //    private DataScope controllerDataScope;

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.yixia.common.annotation.DataScope)")
    public void dataScopePointCut() {
    }


    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) {
        // 在 doBefore 方法中打印线程标识符
        log.info("Thread ID in doBefore: {}", Thread.currentThread().getId());
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (controllerDataScope != null && StringUtils.isNotNull(loginUser)) {
            // 获取当前的用户及相关属性，需提前获取和保存数据权限对应的部门ID集合
            SysUser currentUser = loginUser.getUser();

            String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());

            // 将数据进行保存
            DataScopeParam dataScopeParam = new DataScopeParam(
                    controllerDataScope.deptAlias(),
                    controllerDataScope.userAlias(),
                    controllerDataScope.deptField(),
                    currentUser.isAdmin(),
                    point,
                    currentUser.getRoleList(),
                    currentUser.getDataScope(),
                    currentUser,
                    permission
            );
            threadLocal.set(dataScopeParam);
            log.debug("currentUser.getDataScope() = {}", currentUser.getDataScope());
        }
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private DataScope getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method;
        method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(DataScope.class);
        }
        return null;
    }

    /**
     * ThreadLocal存储对象， 进行临时存储
     */
    @Data
    @AllArgsConstructor
    static class DataScopeParam {
        /**
         * 部门表的别名
         */
        private String deptAlias; /**

         * 用户表的别名
         */
        private String userAlias;

        /**
         * 部门字段名
         */
        private String deptField;

        /**
         * 是否是管理员
         */
        private boolean isAdmin;

        /**
         * 切点信息
         */
        private JoinPoint joinPoint;

        /**
         * 角色列表
         */
        private List<SysRole> roleList;

        /**
         * 数据权限范围
         */
        private Set<Integer> secretary;

        /**
         * 用户信息
         */
        private SysUser currentUser;

        /**
         * 权限字段
         */
        private String permission;
    }


    /**
     * 拼接数据权限sql的条件
     *
     * @param where    sql语句的where条件
     * @param mapperId mapper执行的方法
     */
    public Expression getSqlSegment(Expression where, String mapperId) {
        //TODO 如果是管理员，拥有全部数据权限，不做任何条件语句的拼接，直接返回未处理的where
        // 1. 获取登录者信息
        DataScopeParam dataScopeParam = threadLocal.get();

        // 在 getSqlSegment 方法中打印线程标识符
        log.info("Thread ID in getSqlSegment: {}", Thread.currentThread().getId());

        if (StringUtils.isNull(dataScopeParam)) {
            return where;//是管理员或者数据权限为空则不做过滤
        }

        //TODO mapperId=类路径 + 方法。例如：com.xxx.UserMapper.selectList()
        // 结合自定义注解，通过反射的方式拿到方法上的注解。例如联查SQL，需要指定（别名.数据权限字段）拼接到where后面
        // if("判断mapperId执行方法上是否有注解") {
        //     "拿到value后，传入下面的buildDataFilter()方法里做条件拼接"
        // }


        // 构建查询条件
        String sql = buildDataFilter(dataScopeParam);
        if ("".equals(sql)) {
            return where;
        }
        try {
            Expression expression = CCJSqlParserUtil.parseExpression(sql);
            // 数据权限使用单独的括号 防止与其他条件冲突
            Parenthesis parenthesis = new Parenthesis(expression);
            if (null != where) {
                return new AndExpression(where, parenthesis);
            } else {
                return parenthesis;
            }
        } catch (Exception e) {
            throw new RuntimeException("数据权限解析异常 => " + e.getMessage());
        }
    }

    /**
     * 非常重要！！！
     * 拼接数据权限sql的条件，比如在我们登录成功时，会把用户、角色、数据权限信息缓存到容器上下文或者redis中，当非管理员用户访问某个接口时有查询一些数据，最后会到这个来，这时候我们可以从容器上下文或redis中拿到当前用户的信息，判断用户的数据权限，不同数据权限走不同的拼接SQL
     */
    private String buildDataFilter(DataScopeParam dataScopeParam) {

        String sql = "";


        // 是对dept_id字段进行权限的验证
        if (DEPT_ID.equals(dataScopeParam.getDeptField())) {
            sql = getSqlDeptID(dataScopeParam);
        }


        /*DataScope dataScope = "拿当前用户的数据权限";
        // 部门数据权限
        if (DataScope.DEPARTMENT == dataScope) {
            // 例如：可以返回 "department_id IN (当前用户所属的部门id)"
            return "";
        } else if (DataScope.SELF == dataScope) {
            // 仅本人数据权限，create_by字段是表中的字段，这里不一定是这个字段，根据自己需求在需要做数据权限的表加上自定义得字段
            stringBuilder.append(" create_by").append("= '").append("拿当前用户的username").append("'");
            return stringBuilder.toString();
        }*/
        return sql;
    }

    // 获取sql，与dept_id相关的
    private String getSqlDeptID(DataScopeParam dataScopeParam) {

        StringBuilder sqlString = new StringBuilder();
        List<String> conditions = new ArrayList<String>();

        String permission = dataScopeParam.getPermission();
        SysUser user = SecurityUtils.getUser();

        // 把集合转变为JSQLParser需要的元素列表
        ItemsList itemsList = null;

        for (SysRole role : dataScopeParam.getRoleList()) {
            String dataScope = role.getDataScope();
            // 集合中有该权限，并且该权限不是自定义权限
            if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) {
                continue;
            }
            // 如果权限字符为空，并且角色的权限字符也为空，并且没有包含该角色权限
            if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
                    && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {
                continue;
            }
            // 获取全部的数据
            if (DATA_SCOPE_ALL.equals(dataScope)) {
                sqlString = new StringBuilder();
                conditions.add(dataScope);
                break;
            }
            // 自定义数据，从角色部门表中获取部门ID信息
            else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
                // 把集合转变为JSQLParser需要的元素列表
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", dataScopeParam.getDeptAlias(),
                        role.getRoleId()));
            }
            // 数据权限为本部门数据
            else if (DATA_SCOPE_DEPT.equals(dataScope)) {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", dataScopeParam.getDeptAlias(), user.getDeptId()));
            }
            // 获取本部门及本部门以下的数据局
            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                        dataScopeParam.getDeptAlias(), user.getDeptId(), user.getDeptId()));
            }
            // 只能查看本人的数据
            else if (DATA_SCOPE_SELF.equals(dataScope)) {
                if (StringUtils.isNotBlank(dataScopeParam.getDeptAlias())) {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", dataScopeParam.getDeptAlias(), user.getUserId()));
                } else {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", dataScopeParam.getDeptAlias()));
                }
            }
            conditions.add(dataScope);
        }

        // 多角色情况下，所有角色都不包含传递过来的权限字符，这个时候sqlString也会为空，所以要限制一下,不查询任何数据
        if (StringUtils.isEmpty(conditions)) {
            sqlString.append(StringUtils.format("OR {}.dept_id = 0 ", dataScopeParam.getDeptAlias()));
        }


        String sql = sqlString.toString();
        sql = sql.replaceFirst("OR", "");
        return sql;
    }


    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * 数据权限过滤关键字
     */
    public static final String DATA_SCOPE = "dataScope";

    // 要查询的字段是dept_id
    public static final String DEPT_ID = "dept_id";


}
